// xmc.go
// 本代码由汪碧康编写
//v1.1 create by kang
package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"sort"
	"strings"
	"time"
	"xmc/color"
	"xmc/memcache"
)

func GetStats(address string) *memcache.GeneralStats {
	mclient := memcache.New(address)
	xaddr, err := net.ResolveTCPAddr("tcp4", address)
	if err != nil {
		panic(err)
	}
	statsstruct, err := mclient.Stats(xaddr)
	if err != nil {
		panic(err)
	}
	return statsstruct
}

func GetSettings(address string) *memcache.SettingsStats {
	mclient := memcache.New(address)
	xaddr, err := net.ResolveTCPAddr("tcp4", address)
	if err != nil {
		panic(err)
	}
	settingsstruct, err := mclient.StatsSettings(xaddr)
	if err != nil {
		panic(err)
	}
	return settingsstruct
}

func GetSlabs(address string) map[int]*memcache.SlabStats {
	mclient := memcache.New(address)
	xaddr, err := net.ResolveTCPAddr("tcp4", address)
	if err != nil {
		panic(err)
	}
	slabsmap, err := mclient.StatsSlabs(xaddr)

	if err != nil {
		panic(err)
	}
	return slabsmap
}

func GetItems(address string) map[int]*memcache.ItemStats {
	mclient := memcache.New(address)
	xaddr, err := net.ResolveTCPAddr("tcp4", address)
	if err != nil {
		panic(err)
	}
	itemmap, err := mclient.StatsItems(xaddr)
	if err != nil {
		panic(err)
	}
	return itemmap
}

func GetSizes(address string) {
	var logtime = time.Now().Format("2006010215")
	ipport := strings.Replace(address, ":", "_", -1)
	var tmpfile = "/tmp/" + ipport + "." + "mcstatssize." + logtime + ".log"
	fi, err := os.Open(tmpfile)
	if err != nil {
		//fmt.Printf("no NO.%d data,more data view in /tmp dir/\n", Seelog)
		//return
		mclient := memcache.New(address)
		xaddr, err := net.ResolveTCPAddr("tcp4", address)
		if err != nil {
			panic(err)
		}
		sizemap, err := mclient.StatsSizes(xaddr)
		if err != nil {
			//panic(err)
			fmt.Println("connect memcached time out!")
			return
		}
		ms := map[float64]int64{
			0.5: 0,
			1:   0,
			5:   0,
			10:  0,
			50:  0,
			100: 0,
			200: 0,
			201: 0,
		}
		var sbtotal int64 = 0
		for k, v := range sizemap {
			switch {
			case k < 513:
				ms[0.5] += int64(v)
			case k < 1025:
				ms[1] += int64(v)
			case k < 5121:
				ms[5] += int64(v)
			case k < 10241:
				ms[10] += int64(v)
			case k < 51201:
				ms[50] += int64(v)
			case k < 102401:
				ms[100] += int64(v)
			case k < 204801:
				ms[200] += int64(v)
			default:
				ms[201] += int64(v)
			}
			sbtotal += int64(v)
		}

		//paixu
		var keys []float64
		//var writestring []string
		var aa string
		for k := range ms {
			keys = append(keys, k)
		}
		sort.Float64s(keys)
		f, _ := os.Create(tmpfile)
		for _, k := range keys {
			if k == 201 {
				aa = fmt.Sprintf(">=%5.1fKB : %-10d  %0.2f%%\n", k, ms[k], float64(ms[k])/float64(sbtotal)*100)
			} else {
				aa = fmt.Sprintf("<=%5.1fKB : %-10d  %0.2f%%\n", k, ms[k], float64(ms[k])/float64(sbtotal)*100)
			}
			//writestring = append(writestring, aa)
			_, err := io.WriteString(f, aa)
			if err != nil {
				fmt.Println("write failure!")
			}
		}
		f.Close()
		fi, _ := os.Open(tmpfile)
		fd, _ := ioutil.ReadAll(fi)
		fmt.Println(string(fd))
	} else {
		fd, _ := ioutil.ReadAll(fi)
		fmt.Println("cache")
		fmt.Println(string(fd))
	}
	fi.Close()
}

func TerPrintdetails(add string, isall bool) {
	ss := GetStats(add)
	sst := GetSettings(add)
	var ucktotal, tcktotal, chunksizetotal, usechunksizetotal int64 = 0, 0, 0, 0

	//Totalevict := ss.Evictions

	color.Cyan("====stat==================================================================================================\n")
	color.Red("|  system_time     : %-22s \n", time.Now().Format("2006-01-02 15:04:05"))
	color.Red("|  memcached_time  : %-22s \n", time.Unix(int64(ss.Time), 0).Format("2006-01-02 15:04:05"))
	color.Yellow("|  Version         : %-12v ", ss.Version)
	color.Green("|  RunDays           : %v days \n", ss.Uptime/86400)
	color.Yellow("|  curr_connections: %-12d ", ss.CurrConnections)
	color.Green("|  delete_hits_ratio : %0.4f%% \n", float64(ss.DeleteHits)*100/float64(ss.DeleteHits+ss.DeleteMisses))
	color.Yellow("|  evictions       : %-12d ", ss.Evictions)
	color.Green("|  get_hits_ratio    : %0.4f%% \n", float64(ss.GetHits)*100/float64(ss.CmdGet))
	color.Yellow("|  reclaimed       : %-12d ", ss.Reclaimed)
	color.Green("|  bytes_read        : %0.2fMB \n", float64(ss.BytesRead)/1024/1024)
	color.Yellow("|  total_items     : %-12d ", ss.TotalItems)
	color.Green("|  bytes_written     : %0.2fMB \n", float64(ss.BytesWritten)/1024/1024)
	color.Yellow("|  curr_items      : %-12d ", ss.CurrItems)
	color.Green("|  limit_maxbytes    : %0.2fMB \n", float64(ss.LimitMaxbytes)/1024/1024)
	color.Yellow("|  conn_yields     : %-12d ", ss.ConnYields)
	color.Green("|  bytes             : %0.2fMB \n", float64(ss.Bytes)/1024/1024)
	color.Yellow("|  cmd_flush       : %-12d ", ss.CmdFlush)
	color.Green("|  memused_ratio     : %0.4f%% \n", float64(ss.Bytes)/float64(ss.LimitMaxbytes)*100)
	color.Yellow("|  cmd_set         : %-12d ", ss.CmdFlush)
	color.Green("|  get/set           : %0.4f:1 \n", float64(ss.CmdGet)/float64(ss.CmdSet))

	color.Cyan("====settings===============================================================================================\n")

	color.Green("|  evictions       : %-12v ", sst.Evictions)
	color.Yellow("|  maxconns      : %-12d ", sst.Maxconns)
	color.Red("|  maxbytes      : %0.4fMB  \n", float64(sst.Maxbytes)/1024/1024)
	color.Green("|  detail_enabled  : %-12v ", sst.DetailEnabled)
	color.Yellow("|  num_threads   : %-12d ", sst.NumThreads)
	color.Red("|  chunk_size    : %dByte \n", sst.ChunkSize)
	color.Green("|  reqs_per_event  : %-12d ", sst.ReqsPerEvent)
	color.Yellow("|  tcp_backlog   : %-12d ", sst.TcpBacklog)
	color.Red("|  item_size_max : %0.4fKB \n", float64(sst.ItemSizeMax)/1024)
	color.Green("|  slab_reassign   : %-12v ", sst.SlabReassign)
	color.Yellow("|  slab_automove : %-12v ", sst.SlabAutomove)
	color.Red("|  oldest        : %0.4fHour \n", float64(sst.Oldest)/3600)

	if isall {
		color.Cyan("====slabs==================================================================================================\n")
		color.Cyan("Slabid | ChunkSize | TotalPage | PageOccupy[%%] | ChunkPerPage | ChunkUsedRatio[%%] | MemRequestd[MB] | SlabMemUsedRatio[%%] | MemOccupy[%%] | GetRatio[%%]\n")
		ssl := GetSlabs(add)

		var key []int
		for k := range ssl {
			key = append(key, k)
		}
		sort.Ints(key)
		for _, k := range key {
			ucktotal += int64(ssl[k].UsedChunks)
			tcktotal += int64(ssl[k].TotalChunks)
			chunksizetotal += int64(ssl[k].ChunkSize * ssl[k].TotalChunks)
			usechunksizetotal += int64(ssl[k].ChunkSize * ssl[k].UsedChunks)
			//fmt.Printf("%-8d %-13d %-12d %-15.6f %-16d %-20.6f %-14.4f %-18.4f %-16.4f %0.2f%%\n", k, ssl[k].ChunkSize, ssl[k].TotalPages, float64(ssl[k].TotalPages)*float64(sst.ItemSizeMax)*100/float64(ss.LimitMaxbytes), ssl[k].ChunksPerPage, float64(ssl[k].UsedChunks)*100/float64(ssl[k].TotalChunks), float64(ssl[k].MemRequested)/1024/1024, float64(ssl[k].MemRequested)*100/float64(ssl[k].ChunkSize*ssl[k].TotalChunks), float64(ssl[k].ChunkSize*ssl[k].TotalChunks)*100/float64(ss.LimitMaxbytes), float64(ssl[k].GetHits)*100/float64(ss.CmdGet))
			color.Cyan("%-8d", k)
			color.Green("%-13d %-12d %-15.6f %-16d", ssl[k].ChunkSize, ssl[k].TotalPages, float64(ssl[k].TotalPages)*float64(sst.ItemSizeMax)*100/float64(ss.LimitMaxbytes), ssl[k].ChunksPerPage)
			color.Red("%-20.6f %-14.4f %-18.4f %-16.4f %0.2f%%\n", float64(ssl[k].UsedChunks)*100/float64(ssl[k].TotalChunks), float64(ssl[k].MemRequested)/1024/1024, float64(ssl[k].MemRequested)*100/float64(ssl[k].ChunkSize*ssl[k].TotalChunks), float64(ssl[k].ChunkSize*ssl[k].TotalChunks)*100/float64(ss.LimitMaxbytes), float64(ssl[k].GetHits)*100/float64(ss.CmdGet))
		}

		color.Cyan("==========================================================================================================\n")
		color.Yellow("ChunkUsedRatio:%0.4f%%   UseChunkRamRatio:%0.4f%%   TotalChunkRamRatio:%0.4f%%\n", float64(ucktotal)*100/float64(tcktotal), float64(usechunksizetotal)*100/float64(ss.LimitMaxbytes), float64(chunksizetotal)*100/float64(ss.LimitMaxbytes))
		color.Cyan("====items=================================================================================================\n")
		color.Cyan("Slabid | Number | EvictedRatio | EvictedTime[H] | Outofmemory | Reclaimed | ExpiredUnfetched | EvictedUnfetched\n")

		si := GetItems(add)
		var keys []int
		for k := range si {
			keys = append(keys, k)
		}
		sort.Ints(keys)
		for _, k := range keys {
			color.Cyan("%-8d", k)
			color.Yellow("%-10d %-14.2f %-16.4f", si[k].Number, float64(si[k].Evicted)/float64(ss.Evictions), float64(si[k].EvictedTime)/3600)
			color.Green("%-16d %-14d %-14d %-10d\n", si[k].Outofmemory, si[k].Reclaimed, si[k].ExpiredUnfetched, si[k].EvictedUnfetched)

		}
		color.Cyan("====sizes==================================================================================================\n")
		//GetSizes(add)
	}
}

func Graphapi(add string) {
	ss := GetStats(add)
	//sst := GetSettings(add)
	fmt.Printf("mc_time=%v\n", ss.Time)
	fmt.Printf("curr_connections=%v\n", ss.CurrConnections)
	fmt.Printf("bytes_read=%d\n", ss.BytesRead)
	fmt.Printf("bytes_written=%d\n", ss.BytesWritten)
	fmt.Printf("cmd_set=%d\n", ss.CmdSet)
	fmt.Printf("cmd_get=%d\n", ss.CmdGet)
	fmt.Printf("curr_items=%d\n", ss.CurrItems)
	fmt.Printf("total_items=%d\n", ss.TotalItems)
	fmt.Printf("evictions=%d\n", ss.Evictions)
	fmt.Printf("memused_ratio=%0.2f\n", float64(ss.Bytes)/float64(ss.LimitMaxbytes))
	fmt.Printf("get_hits_ratio=%0.2f\n", float64(ss.GetHits)/float64(ss.CmdGet))
	fmt.Printf("delete_hits_ratio=%0.2f\n", float64(ss.DeleteHits)/float64(ss.DeleteHits+ss.DeleteMisses))
	//fmt.Printf("evictions_ratio=%d\n", ss.Evictions)
}

func Help() {
	color.Cyan("Version: gomcstat_v1.1\n")
	color.Cyan("Usage:./xmcstat --i=\"192.168.1.1:11211\"\n")
	color.Cyan("Usage:./xmcstat\n")
	color.Cyan("Usage:./xmcstat -g=true\n")
	color.Cyan("Usage:./xmcstat -a=true,print all info\n")
	color.Cyan("Usage:./xmcstat -s=true,print statsize\n")
	color.Cyan("defautl ip=127.0.0.1 default port=11211\n")
	color.Red("==document== \n")
	fmt.Printf("delete_hits_ratio  :  delete_hits / delete_hits+delete_miss\n")
	fmt.Printf("get_hits_ratio     :  get_hits / cmd_get\n")
	fmt.Printf("memused_ratio      :  bytes / limit_maxbytes\n")
	fmt.Printf("get/set            :  cmd_get / cmd_set\n")
	fmt.Printf("PageOccupy         :  slabs total_pages * item_size_max / limit_maxbytes\n")
	fmt.Printf("ChunkUsedRatio     :  slabs used_chunks / total_chunks\n")
	fmt.Printf("SlabMemUsedRatio   :  slabs mem_requested/chunk_size * total_chunks\n")
	fmt.Printf("MemOccupy          :  slabs chunk_size * total_chunks / limit_maxbytes\n")
	fmt.Printf("GetRatio           :  slabs get_hits / cmd_get,exclude get_miss\n")
	fmt.Printf("ChunkUsedRatio     :  all slabs used_chunks / total_chunks\n")
	fmt.Printf("UseChunkRamRatio   :  all slabs chunk_size * used_chunks / limit_maxbytes\n")
	fmt.Printf("TotalChunkRamRatio :  all slabs chunk_size * total_chunks / limit_maxbytes\n")
	//fmt.Printf("EvictedRatio      : \n")
}

func main() {
	help := flag.Bool("h", false, "show help")
	//Json := flag.Bool("j", false, "show json")
	Graph := flag.Bool("g", false, "show Graph value")
	McAddress := flag.String("i", "127.0.0.1:11211", "memcached ip")
	showColor := flag.Bool("c", true, "show or not show color")
	mcsize := flag.Bool("s", false, "show or not show color")
	printall := flag.Bool("a", false, "show or not show color")
	//McPort := flag.String("p", "11211", "memcachd port")
	flag.Parse()
	if *showColor {
		color.Enable()
	}

	if *mcsize {
		GetSizes(*McAddress)
		return
	}

	if *help {
		Help()
		return
	}

	if *Graph {
		Graphapi(*McAddress)
		return
	}
	//GetSizes(*McAddress)
	TerPrintdetails(*McAddress, *printall)

}
